/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C)  2004 - 2005 Steve Rodgers
*
* Steve Rodgers <hwstar@rodgers.sdcoxmail.com>
* 
* C Implementation: app_alarmsender
*
* Author: Gregory Gascon <gregory@domo-safety>, DomoSafety (C) 2010
*
* Copyright:  
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*
*/

/*! \file
 * \brief Central Station Alarm sender for Ademco Contact ID
 * \author Gregory Gascon <gregory@domo-safety.com>
 *
 * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
 *
 * Use at your own risk. Please consult the GNU GPL license document included with Asterisk.         *
 *
 * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
 *
 * \ingroup applications
 */

#include "asterisk.h"

ASTERISK_FILE_VERSION(__FILE__, "$Revision: 0.90 $")

#include <math.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/wait.h>

#include "asterisk/app.h"
#include "asterisk/alaw.h"
#include "asterisk/callerid.h"
#include "asterisk/channel.h"
#include "asterisk/config.h"
#include "asterisk/dsp.h"
#include "asterisk/file.h"
#include "asterisk/localtime.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/translate.h"
#include "asterisk/ulaw.h"
#include "asterisk/utils.h"

#define ALMSDR_CONFIG "alarmsender.conf"
#define ADEMCO_CONTACT_ID "ADEMCO_CONTACT_ID"

static char *app = "AlarmSender";

/*** DOCUMENTATION
	<application name="AlarmSender" language="en_US">
		<synopsis>
			Provide support for sending alarm reports from a burglar or fire alarm panel.
		</synopsis>
		<syntax />
		<description>
			<para>This application should be called whenever there is an alarm panel calling in to dump its events.
			The application will handshake with the alarm receiver, and send events, validate them, handshake them.
			The configuration file also contains settings for DTMF timing, and for the loudness of the
			acknowledgement tones.</para>
			<note><para>Only 1 signalling format is supported at this time: Ademco Contact ID.</para></note>
		</description>                                      
		<see-also>
			<ref type="filename">alarmsender.conf</ref>
		</see-also>
	</application>
 ***/
/* Config Variables */        


/** By ADEMCO ContactID specifications for sending DTMF alarm:
* Burst ON time - 50 msec. (50 min.,60 max.)
* Burst OFF time- 50 msec. (50 min.,60 max.)
*/
static int DTMF_length = 55;
static int DTMF_pause = 55;
static int firstBurstHdshkLength = 60;
static int secondBurstHdshkLength = 60;
static int burstKissoffLength = 150;
static int pauseLength = 600;
static int firstBurstHdshkTimeout = 900000;
static int secondBurstHdskTimeout = 120;
static int kissoffTimeout = 200;
static int lengthError = 5;

inline int send_dtmf_digits(struct ast_channel *chan, char *digit_string);

/*
* Receive a single tone burst for a specified minimum duration and frequency.
* Returns 0 if successful
*/
static int receive_tone_burst(struct ast_channel *chan, int freq, int duration, int timeout)
{
	int res = 0;
	int total_received_len = 0;
	int tries = 0;
	int x;
	unsigned char *odata;
	short *shortdata = NULL;
	int len;
	
	struct ast_frame *f;
	float* tmp;
	tmp = (float*) malloc(sizeof(float) * 105);
	struct timeval lastdigittime;
	
	struct ast_frame initFrame;
	initFrame.frametype = AST_FRAME_VOICE;
	initFrame.subclass = AST_FORMAT_ALAW;
	initFrame.offset = AST_FRIENDLY_OFFSET;
	initFrame.mallocd = 0;
	initFrame.data.ptr = "";
	initFrame.datalen = 0;
	initFrame.samples = 0;

	lastdigittime = ast_tvnow();
	ast_verb(4, "AlarmSender : Launching receving loop\n");

	/** Uncomment those line to save received data as .wav. Useful to Debug */
	char filename[100] = "/tmp/output";
	char tempChar[50];
	sprintf(tempChar, "%d", freq);
	strcat(filename, tempChar);
	strcat(filename, "Hz-");
	sprintf(tempChar, "%d", duration);
	strcat(filename, tempChar);
	strcat(filename, "ms");
	struct ast_filestream *s = NULL;
	s = ast_writefile(filename, "wav", NULL, 1, 0, AST_FILE_MODE);
 
	for (total_received_len = 0; 
		total_received_len < duration - ceil(duration * lengthError / 100) 
		&& res == 0;) {

		/* if outa time, leave */
		if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > timeout) {
			ast_verb(4, "AlarmSender : Timeout on %s\n", chan->name);
			ast_debug(4,"AlarmSender : timeout on chan %s\n",chan->name);
			res = 1;
			break;
		}
		if ((res = ast_waitfor(chan, -1) < 0)) {
			ast_verb(4, "AlarmSender : Waitfor returned %d\n", res);
			continue;
		}

 		f = ast_read(chan);
		if (f == NULL) {
			ast_verb(4, "AlarmSender : Cannot read on channel\n");
			res = -1;
			break;
		}
		
		if (f->frametype != AST_FRAME_VOICE) {
			ast_verb(4, "AlarmSender : Not a ALAW frame, trying with next frame\n");
			while(ast_write(chan, &initFrame) && tries < 10)
			{		
				ast_safe_sleep(chan, 100);
				tries++;
			}
			ast_safe_sleep(chan, 50);
			ast_frfree(f);
			continue;
		}

		/* If they hung up, leave */
		if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
			if (f->data.uint32) {
				chan->hangupcause = f->data.uint32;
			}
			ast_verb(4, "AlarmSender : Channel hangs while trying to receive tone... Exiting\n");
			ast_frfree(f);
			res = -1;
			break;
		}

		odata = f->data.ptr;
		len = f->datalen;
		// Converting data to a short array
		switch (f->subclass) {
		case AST_FORMAT_SLINEAR:
			shortdata = f->data.ptr;
			len = f->datalen / 2;
			break;
		case AST_FORMAT_ULAW:
			shortdata = alloca(f->datalen * 2);
			for (x = 0;x < len; x++) {
				shortdata[x] = AST_MULAW(odata[x]);
			}
			break;
		case AST_FORMAT_ALAW:
			shortdata = alloca(f->datalen * 2);
			for (x = 0; x < len; x++) {
				shortdata[x] = AST_ALAW(odata[x]);
			}
			break;
		default:
			
			ast_log(LOG_WARNING, "Cannot decode received data\n");
			res = -1;
			break;
		}

		if (f->frametype == AST_FRAME_VOICE)
		{	
			ast_writestream(s, f);
			struct ast_dsp* dsp;
			dsp = ast_dsp_new();
			int totalsilence = 0;
			int totalnoise = 0;
			if(ast_dsp_silence(dsp, f, &totalsilence) != 0)
			{
				//ast_verb(4, "AlarmSender : Silence detected : %ds\n", totalsilence);	
			}
			else if(ast_dsp_noise(dsp, f, &totalnoise) != 0)
			{
				//ast_verb(4, "AlarmSender : Noise detected : %ds\n", totalnoise);
			}
			else 
			{
				int resultat = ast_tone_detection(dsp, freq, len, 16, shortdata);
				
				if(resultat == 0)
				{
					total_received_len += len / 8;
				}
				lastdigittime = ast_tvnow();
				ast_verb(4, "AlarmSender : Total received duration for frequency %d : %d\n",
				freq, total_received_len);
			}
			ast_dsp_free(dsp);
		}
		ast_frfree(f);
	}
        ast_closestream(s);
	return res;
}

/*
* Send a string of DTMF digits where the length of the digit string is known in advance.
* Valids digits are : "0123456789*#abcdfABCDF"
* Returns 0 if all digits successfully received.
* Returns 1 if a digit time out occurred
*/
inline int send_dtmf_digits(struct ast_channel *chan, char *digit_string)
{
	int res = ast_dtmf_stream(chan, NULL, digit_string, DTMF_pause, DTMF_length);
	if(res < 0)
	{
		ast_verb(1, "AlarmSender: Error while sending DTMF datas %d\n", res);
		return 1; 
	}
	ast_verb(1, "AlarmSender: Sending DTMF digits successful !!\n");
	return 0;
}

/*
* Detect handshake in ADEMCO ContactID protocol
*/
static int receive_handshake(struct ast_channel *chan)
{
	int res;
	ast_verb(4, "AlarmSender: Verifying 1400Hz 100ms burst (ACK)\n");
	res = receive_tone_burst(chan, 1400.0, firstBurstHdshkLength, firstBurstHdshkTimeout);
	if (!res) {
		ast_verb(4, "AlarmSender: Verifying 2300Hz 100ms burst (ACK)\n");
		res = receive_tone_burst(chan, 2300.0, secondBurstHdshkLength, secondBurstHdskTimeout);
	}
	else
		ast_verb(4, "AlarmSender: Cannot recognize 1400Hz tone, retrying...\n");
	//usleep(500);
	if (!res)
		res = ast_safe_sleep(chan, pauseLength);
	else 
		ast_verb(4, "AlarmSender: Cannot recognize 2300Hz tone, retrying...\n");
	return res;
}

/*
* Detect kissoff in ADEMCO ContactID protocol
*/
static int receive_kissoff(struct ast_channel *chan)
{
	int res;
	res = receive_tone_burst(chan, 1400.0, burstKissoffLength, kissoffTimeout);
	if (!res)
		res = ast_safe_sleep(chan, pauseLength);
	else 
		ast_verb(4, "AlarmSender: Cannot recognize 1400Hz tone, restarting...\n");
	return res;
}

/*
* This function implements the logic to send the Ademco contact ID format.
*
* The function will return 0 when the sender has finished, else a -1 if there was a problem.
*/
static int send_ademco_contact_id( struct ast_channel *chan, void *data)
{
	int i, j;
	int res = 0;
	int checksum;
	
	static char digit_map[15] = "0123456789*#ABC";
	static unsigned char digit_weights[15] = {10,1,2,3,4,5,6,7,8,9,11,12,13,14,15};

	/* Calculate checksum */

	for (j = 0, checksum = 0; j < 15; j++) {
		for (i = 0; i < sizeof(digit_map); i++) {
			if (digit_map[i] == (char)((char*)(data))[j])
				break;
		}
		if (i == 16)
			break;
		checksum += digit_weights[i];
	}

	if (i == 16) {
		ast_verb(2, "AlarmSender: Bad DTMF character, trying again\n");
		return -1; /* Bad character */
	}
	
	int qt = checksum / 15;
	qt += 1;
	checksum = (qt * 15) - checksum;
	if(checksum == 0) checksum = 15;
	char digits_to_send[16];
	memcpy(digits_to_send, data, 15);
	
	digits_to_send[15] = digit_map[checksum];
	ast_verb(4, "AlarmSender : digits_to_send : %s with checksum : %c\n", digits_to_send, digit_map[checksum]);
		
	res = receive_handshake(chan);
	if (!res)
		res = send_dtmf_digits(chan, digits_to_send);
	if (res != 0) {
		ast_verb(4, "AlarmSender: Sending DTMF exiting while trying to send datas...\n");
		res = -1;
	}
	if(!res)
	{
		res = receive_kissoff(chan);
	}
	return res;
}

/*
* This is the main function called by Asterisk Core whenever the App is invoked in the extension logic.
* This function will always return 0.
*/
static int alarmsender_exec(struct ast_channel *chan, void *data)
{
	int res = 0;
	char signalling_type[64] = "";

	/* Set write and read formats to ULAW */
	ast_verb(4, "AlarmSender: Setting read and write formats to ALAW\n");

	if (ast_set_write_format(chan,AST_FORMAT_ALAW)) {
		ast_log(LOG_WARNING, "AlarmSender: Unable to set write format to A-law on %s\n",chan->name);
		return -1;
	}

	if (ast_set_read_format(chan,AST_FORMAT_SLINEAR)) {
		ast_log(LOG_WARNING, "AlarmSender: Unable to set read format to A-law on %s\n",chan->name);
		return -1;
	}

	/* Set default values for this invocation of the application */
	ast_copy_string(signalling_type, ADEMCO_CONTACT_ID, sizeof(signalling_type));

	/* Answer the channel if it is not already */
	if (chan->_state != AST_STATE_UP) {
		if ((res = ast_answer(chan)))
			return -1;
	}
	
	ast_verb(4, "AlarmSender: Sending ademco contact ID\n");
	
	/* Attempt to send the events */
	if (!res) {
		ast_verb(4, "AlarmSender: Determining protocol ademco contact ID...\n");
		/* Determine the protocol to send in advance */
		if(!strcmp(signalling_type, ADEMCO_CONTACT_ID))
		{
			ast_verb(4, "AlarmSender: Protocol ademco contact ID determined !\n");
			int i;
			int hasFinished = 1;
			for(i = 0; i < 4 && hasFinished == 1; i++)
			{
				ast_verb(4, "AlarmSender: Trying to send for %d time (max 4)\n", i + 1);
				res = send_ademco_contact_id(chan, data);
				if(!res) 
				{
					hasFinished = 0;
					ast_verb(4, "AlarmSender: Ademco Contact ID sent successfully !\n");
				}
			}
		}
		else;
			res = -1;
	}
	
	return 0;
}

/*
* Load the configuration from the configuration file                            
*/
static int load_config(void)
{
	struct ast_config *cfg;
	const char *p;
	struct ast_flags config_flags = { 0 };

	/* Read in the config file */
	cfg = ast_config_load(ALMSDR_CONFIG, config_flags);

	if (!cfg) {
		ast_verb(4, "AlarmSender: No config file\n");
		return 0;
	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
		ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", ALMSDR_CONFIG);
		return 0;
	} else {
		p = ast_variable_retrieve(cfg, "general", "DTMF_length");
		if(p) {
			DTMF_length = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "DTMF_pause");
		if(p) {
			DTMF_pause = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "firstBurstHdshkLength");
		if(p) {
			firstBurstHdshkLength = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "secondBurstHdshkLength");
		if(p) {
			secondBurstHdshkLength = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "burstKissoffLength");
		if(p) {
			burstKissoffLength = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "firstBurstHdshkTimeout");
		if(p) {
			firstBurstHdshkTimeout = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "secondBurstHdskTimeout");
		if(p) {
			secondBurstHdskTimeout = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "DTMF_pause");
		if(p) {
			DTMF_pause = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "kissoffTimeout");
		if(p) {
			kissoffTimeout = atoi(p);
		}

		p = ast_variable_retrieve(cfg, "general", "lengthError");
		if(p) {
			lengthError = atoi(p);
		}

		ast_config_destroy(cfg);
	}
	return 1;
}

/*
* These functions are required to implement an Asterisk App.
*/
static int unload_module(void)
{
	return ast_unregister_application(app);
}

static int load_module(void)
{
	if (load_config()) {
		if (ast_register_application_xml(app, alarmsender_exec))
			return AST_MODULE_LOAD_FAILURE;
		return AST_MODULE_LOAD_SUCCESS;
	} else
		return AST_MODULE_LOAD_DECLINE;
}

AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Alarm Sender for Asterisk");
